package ezXpns.storage;

import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.lucene.document.*;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.store.Directory;

import ezXpns.analysis.Transform;


//@author A0088827L
class SearchIndexer<T> implements StorageListener<T>, AutoCloseable {
	
	public SearchIndexer(Directory directory, Transform<Pair<UUID, T>, Document> converter) throws IOException {
		this.converter = converter;
		IndexWriterConfig config = new IndexWriterConfig(LuceneConfig.LUCENE_VERSION, LuceneConfig.ANALYZER);
		indexWriter = new IndexWriter(directory, config);
		indexWriter.commit();
	}
	
	public IndexWriter getIndexWriter() {
		return indexWriter;
	}

	@Override
	public void onAdd(UUID id, T obj) {
		Document doc = converter.apply(new ImmutablePair<UUID, T>(id, obj));
		modifyIndex(ADD_DOCUMENT, doc);
	}

	@Override
	public void onRemove(UUID id, T obj) {
		modifyIndex(REMOVE_DOCUMENT, id);
	}

	@Override
	public void onReplace(UUID id, T oldObj, T newObj) {
		Document newDocument = converter.apply(new ImmutablePair<UUID, T>(id, newObj));
		modifyIndex(UPDATE_DOCUMENT, new ReplaceDocArgs(id, newDocument));
	}
	
	@Override
	public void close() throws Exception {
		cancelCommit();
		if(indexWriter != null) {
			indexWriter.commit();
			indexWriter.close();
		}
	}
	
	private <ArgT> void modifyIndex(IndexAction<ArgT> indexAction, ArgT arg) {
		//TODO: rebuild index on error
		try {
			indexAction.apply(indexWriter, arg);
			indexWriter.commit();
			//scheduleCommit();
		} catch (OutOfMemoryError e) {
			//close and rebuild
			try {
				indexWriter.close();
			} catch (IOException e1) {
				// TODO warning
				e1.printStackTrace();
			}
		} catch (Exception e) {
			//rebuild
			e.printStackTrace();
		}
	}
	
	private void cancelCommit() {
		if(timer != null) {
			timer.cancel();
			timer = null;
		}
	}
	
	@SuppressWarnings("unused")
	private void scheduleCommit() {
		cancelCommit();
		timer = new Timer();
		timer.schedule(new TimerTask() {
			@Override
			public void run() {
				try {
					indexWriter.commit();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			
		}, 4000);
	}
	
	private IndexWriter indexWriter;
	private Transform<Pair<UUID, T>, Document> converter;
	private Timer timer = null;
	
	private static final IndexAction<Document> ADD_DOCUMENT = new IndexAction<Document>() {
		@Override
		public void apply(IndexWriter writer, Document doc) 
				throws IOException,	CorruptIndexException {
			writer.addDocument(doc);
		}
	};
	
	private static final IndexAction<UUID> REMOVE_DOCUMENT = new IndexAction<UUID>() {
		@Override
		public void apply(IndexWriter writer, UUID id) 
				throws IOException,	CorruptIndexException {
			Term term = new Term("id", id.toString());
			writer.deleteDocuments(term);
		}
	};
	
	private static final IndexAction<ReplaceDocArgs> UPDATE_DOCUMENT = new IndexAction<ReplaceDocArgs>() {
		@Override
		public void apply(IndexWriter writer, ReplaceDocArgs args) 
				throws IOException,	CorruptIndexException {
			Term term = new Term("id", args.id.toString());
			writer.updateDocument(term, args.document);
		}
	};
		
	private static interface IndexAction<T> {
		void apply(IndexWriter writer, T arg) throws IOException, CorruptIndexException;
	}
	
	private static class ReplaceDocArgs {
		public ReplaceDocArgs(UUID id, Document document) {
			this.id = id;
			this.document = document;
		}
		public UUID id;
		public Document document;
	}
}
